﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using System.IO;
using System.Diagnostics;
using System.Runtime.Serialization;


namespace Common.Model
{
    public class InvalidHashException : Exception
    {
        public InvalidHashException() : base() { }
        public InvalidHashException(string message) : base(message) { }
        public InvalidHashException(string message, Exception innerException) : base(message, innerException) { }
    }
    public class FileNotValidException : Exception
    {
        public FileNotValidException() : base() { }
        public FileNotValidException(string message) : base(message) { }
        public FileNotValidException(string message, Exception innerException) : base(message, innerException) { }
    }

    [DataContract]
    public class CProtokollEintrag
    {
        #region getter setter
        [DataMember]
        public CSpielfeld.FieldColor[,] Feld { get; set; }
        public CSpielfeld.FieldColor this[CPoint p]
        {
            get
            {
                return Feld[p.X, p.Y];
            }
        }
        [DataMember]
        public int CurrentSpielerIndex { get; set; }
        [DataMember]
        public bool CurrentSpielerIsKI { get; set; }
        #endregion

        public override string ToString()
        {
            try
            {
                return "ProtokollEintrag=[CurrentSpielerIndex=" + CurrentSpielerIndex + ", CurrentSpielerIsKI=" + CurrentSpielerIsKI.ToString() + ", Feld=" + Feld.ToString() + "]";
            }
            catch (Exception)
            {
                return base.ToString();
            }
        }
    }

    [DataContract]
    public class CProtokoll
    {
        public static readonly string CurrentProtokollVersion = "0.1";

        /* müssen für json serialisierung public sein */
        #region datamembers
        [DataMember]
        public bool showPossibleMoves;
        [DataMember]
        public List<CProtokollEintrag> Protokolleintraege = new List<CProtokollEintrag>();
        [DataMember]
        public List<CSpieler> Spieler = new List<CSpieler>();
        [DataMember]
        public List<CKISpieler> KISpieler = new List<CKISpieler>();
        [DataMember]
        public int CurrentMovesIndex = 0;
        [DataMember]
        public DateTime Created;
        [DataMember]
        public string ProtokollVersion
        {
            get
            {
                if (_ProtokollVersion == null) _ProtokollVersion = CProtokoll.CurrentProtokollVersion;
                return _ProtokollVersion;
            }
            set
            {
                _ProtokollVersion = value;
            }
        }
        private string _ProtokollVersion;
        #endregion

        public string Pfad;//Pfad der Datei, in welche zur Zeit geschrieben wird
        private bool _isSaved = false;

        public enum Moves { Next = 1, Previous = -1, Current = 0 }

        public static readonly string suffix = ".jos";

        #region getter / setter
        public CProtokollEintrag this[int index]
        {
            get
            {
                return Protokolleintraege[index];
            }
        }
        public CProtokollEintrag this[Moves m]
        {
            get
            {
                CurrentMovesIndex += (int)m;
                return this[CurrentMovesIndex];
            }
        }
        public CProtokollEintrag Current
        {
            get
            {
                return Protokolleintraege[CurrentMovesIndex];
            }
        }
        public bool hasNext
        {
            get
            {
                return this.CurrentMovesIndex + 1 < Protokolleintraege.Count;
            }
        }
        public bool hasPrev
        {
            get
            {
                return this.CurrentMovesIndex - 1 >= 0;
            }
        }
        public bool isSaved
        {
            get
            {
                return _isSaved;
            }
            private set
            {
                _isSaved = value;
            }
        }
        #endregion

        #region konstruktor
        public CProtokoll() { }
        public CProtokoll(List<CSpieler> spieler, bool showPossibleMoves)
        {
            Created = DateTime.Now;
            this.showPossibleMoves = showPossibleMoves;
            this.KISpieler = spieler.Where((s) => s.GetType() == typeof(CKISpieler)).Cast<CKISpieler>().ToList();
            this.Spieler = spieler.Where((s) => s.GetType() != typeof(CKISpieler)).ToList();
        }
        #endregion

        /// <summary>
        /// fügt einen eintrag hinzu
        /// </summary>
        /// <param name="Feld"></param>
        /// <param name="setAsCurrent"></param>
        public void AddEintrag(CSpielfeld SPFeld, CSpieler current, bool setAsCurrent = true)
        {
            CProtokollEintrag eintrag = new CProtokollEintrag()
            {
                Feld = (new CSpielfeld(SPFeld)).Feld
            };

            if (eintrag.CurrentSpielerIsKI = current.GetType() == typeof(CKISpieler))
            {
                eintrag.CurrentSpielerIndex = KISpieler.IndexOf(current as CKISpieler);
            }
            else
            {
                eintrag.CurrentSpielerIndex = Spieler.IndexOf(current);
            }

            AddEintrag(eintrag, setAsCurrent);
        }

        /// <summary>
        /// fügt einen eintrag hinzu
        /// </summary>
        /// <param name="eintrag"></param>
        /// <param name="setAsCurrent"></param>
        public void AddEintrag(CProtokollEintrag eintrag, bool setAsCurrent = true)
        {
            for (int i = CurrentMovesIndex; i < Protokolleintraege.Count - 1; )
            {
                Protokolleintraege.RemoveAt(Protokolleintraege.Count - 1);
            }

            Protokolleintraege.Add(eintrag);

            if (setAsCurrent)
                CurrentMovesIndex = Protokolleintraege.Count - 1;

            isSaved = false;
        }

        /// <summary>
        /// erstellt eine datei am angegebenen pfad mit den entsprechenden daten
        /// </summary>
        /// <param name="path"></param>
        public void export(string path = null)
        {
            try
            {
                if (path != null) Pfad = path;
                else if (Pfad == null) throw new Exception("Beim ersten Speichern muss ein Pfad angegeben werden");

                string data = JsonConvert.SerializeObject(this);
                StreamWriter sw = new StreamWriter(Pfad, false, Encoding.Default);
                sw.Write(getHash(data) + data);
                sw.Close();

                isSaved = true;
            }
            catch (Exception e)
            {
                if (e is IOException)
                {
#if DEBUG
                    Debug.Print(e.Message + Environment.NewLine + e.StackTrace);
#endif
                    return;
                }
                throw;
            }
        }

        /// <summary>
        /// importiert ein protokoll
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public static CProtokoll import(string path)
        {
            string content;
            StreamReader sr = null;
            try
            {
                sr = new StreamReader(path, Encoding.Default);
                content = sr.ReadToEnd();
                string hash = content.Substring(0, getHash(content).Length);
                content = content.Substring(hash.Length);

                if (getHash(content) != hash) throw new InvalidHashException("Die Datei scheint manipuliert worden zu sein.");

                CProtokoll p = JsonConvert.DeserializeObject<CProtokoll>(content);

                if (p.ProtokollVersion != CProtokoll.CurrentProtokollVersion) throw new FileNotValidException("Das Format der Datei scheint veraltet zu sein.");
                if (p.Protokolleintraege.Count < 1) throw new FileNotValidException("Die Datei scheint defekt zu sein.");

                p.Pfad = path;

                return p;
            }
            finally
            {
                try
                {
                    sr.Close();
                }
                catch (Exception e)
                {
#if DEBUG
                    Debug.Print(e.StackTrace);
#endif
                }
            }

        }

        /// <summary>
        /// generiert ein sha256 hash des angegebenen strings
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public static string getHash(string data)
        {
            string hash = "", salt = "]+IKl-6[x=-Iqy9,]d/ha.<X0n,/NV+!|~Y-v^;-%3}abW)HN>JG,}{hR:&}5?vj";//salt, falls die standard hashfunktion (sha256) herausgefunden wird, kann der hash trotzdem nicht einfach reproduziert werden.
            (new System.Security.Cryptography.SHA256Managed()).ComputeHash(Encoding.ASCII.GetBytes(data + salt)).ToList().ForEach((b) => hash += b.ToString("x2"));
            return hash;
        }
    }
}
